#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string.h>
#include <assert.h>
#include <sys/stat.h>
#include <fcntl.h>

#define COM_SIZE 1024
#define ARGV_SIZE 64
#define DEF_CHAR " "

void split(char* argv[ARGV_SIZE], char* ps)
{
  assert(argv && ps);

  //调用 C语言 中的 strtok 函数分割字符串
  int pos = 0;
  argv[pos++] = strtok(ps, DEF_CHAR);  //有空格就分割
  while(argv[pos++] = strtok(NULL, DEF_CHAR));  //不断分割

  argv[pos] = NULL; //确保安全
}

void showEnv()
{
  extern char** environ;  //使用当前进行的环境变量表
  int pos = 0;
  for(; environ[pos]; printf("%s\n", environ[pos++]));
}

//枚举类型，用于判断不同的文件打开方式
enum redir
{
  REDIR_INPUT = 0,
  REDIR_OUTPUT,
  REDIR_APPEND,
  REDIR_NONE
}redir_type = REDIR_NONE; //创建对象 redir_type，默认为 NONE

//检查是否出现重定向符
char* checkDir(char* command)
{
  //从右往左遍历，遇到 > >> < 就置为 '\0'
  size_t end = strlen(command); //与返回值相匹配
  char* ps = command + end; //为了避免出现无符号-1，这里采取错位的方法
  while(end != 0)
  {
    if(command[end - 1] == '>')
    {
      if(command[end - 2] == '>')
      {
        command[end - 2] = '\0';
        redir_type = REDIR_APPEND;
        return ps;
      }
      
      command[end - 1] = '\0';
      redir_type = REDIR_OUTPUT;
      return ps;
    }
    else if(command[end - 1] == '<')
    {
      command[end - 1] = '\0';
      redir_type = REDIR_INPUT;
      return ps;
    }

    //如果不是空格，就可以更新 ps指向
    if(*(command + end - 1) != ' ')
      ps = command + end - 1;
    
    end--;
  }

  return NULL;  //如果没有重定向符，就返回空
}

int main()
{
  char myEnv[COM_SIZE][ARGV_SIZE];  //大小与前面有关
  int env_pos = 0;  //专门维护缓冲区
  int exit_code = 0;  //保存退出码的全局变量
  //这是一个始终运行的程序：bash
  while(1)
  {
    char command[COM_SIZE]; //存放指令的数组（缓冲区）
    
    //打印提示符
    printf("[User@myBash default]$ ");
    fflush(stdout);

    //读取指令
    //因为有空格，所以需要逐行读取
    fgets(command, COM_SIZE, stdin);
    assert(command);  //不能输入空指令
    (void)command; //防止在 Release 版本中出错

    command[strlen(command) - 1] = '\0';  //将最后一个字符 \n 变成 \0

    //重定向
    //在获取指令后进行判断
    //如果成立，则获取目标文件名 filename
    char *filename = checkDir(command);

    //指令分割
    //将连续的指令分割为 argv 表
    char* argv[ARGV_SIZE];
    split(argv, command);
  
   //特殊处理
   //颜色高亮处理，识别是否为 ls 指令
   if(strcmp(argv[0], "ls") == 0)
   {
     int pos = 0;
     while(argv[pos++]); //找到尾
     argv[pos - 1] = (char*)"--color=auto"; //添加此字段
     argv[pos] = NULL; //结尾
   }

   //目录间移动处理
   if(strcmp(argv[0], "cd") == 0)
   {
     //直接调用接口，然后 continue 不再执行后续代码
     if(strcmp(argv[1], "~") == 0)
       chdir("/home");  //回到家目录
     else if(strcmp(argv[1], "-") == 0)
       chdir(getenv("OLDPWD"));
     else if(argv[1])
       chdir(argv[1]);  //argv[1] 中就是路径
     continue;  //终止此次循环
   }

    //环境变量相关
    if(strcmp(argv[0], "export") == 0)
    {
      if(argv[1])
      {
        strcpy(myEnv[env_pos], argv[1]);
        putenv(myEnv[env_pos++]);
      }
      continue; //一样需要提前结束循环
    }

    //环境变量表
    if(strcmp(argv[0], "env") == 0)
    {
      showEnv();  //调用函数，打印父进程的环境变量表
      continue; //提前结束本次循环
    }

    //echo 相关
    //只有 echo $ 才做特殊处理（环境变量+退出码）
    if(strcmp(argv[0], "echo") == 0 && argv[1][0] == '$')
    {
      if(argv[1] && argv[1][0] == '$')
      {
        if(argv[1][1] == '?')
          printf("%d\n", exit_code);
        else
          printf("%s\n", getenv(argv[1] + 1));
      }

      continue;
    }

    //子进程进行程序替换
    pid_t id = fork();
    if(id == 0)
    {
      //判断是否需要进行重定向
      if(redir_type == REDIR_INPUT)
      {
        int fd = open(filename, O_RDONLY);
        dup2(fd, 0);  //更改输入，读取文件 filename
      }
      else if(redir_type == REDIR_OUTPUT)
      {
        int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
        dup2(fd, 1);  //写入
      }
      else if(redir_type == REDIR_APPEND)
      {
        int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
        dup2(fd, 1);  //追加
      }

      //直接执行程序替换，这里使用 execvp
      execvp(argv[0], argv);

      exit(168); //替换失败后返回
    }
  
    //父进程等待子进程终止
    int status = 0;
    waitpid(id, &status, 0);  //在等待队列中阻塞
    exit_code = WEXITSTATUS(status);
    if(WIFEXITED(status))
    {
      //假如程序替换失败
      if(exit_code == 168)
        printf("%s: Error - %s\n", argv[0], "The directive is not yet defined");
    }
    else
      printf("process run fail! [code_dump]:%d [exit_signal]:%d\n", (status >> 7) & 1, status & 0x7F);  //子进程异常终止的情况
  }

  return 0;
}
